/*

 Henry
 A fast inference engine for Hobbs et al. (93)'s weighted abduction.

 Naoya Inoue, Tohoku University
 naoya-i@ecei.tohoku.ac.jp
 http://www.cl.ecei.tohoku.ac.jp/~naoya-i/
 
 */

#include <stack>

#include <fstream>
#include <boost/format.hpp>
#include "string.h"
#include "pcrecpp.h"

#include "knowledge_base.hpp"
#include "inference_engine.hpp"

#include "henry.hpp"

void _usage() {
  
  cout <<
    "Hey, what am I supposed to do?" << endl <<
    "                        -- John Henry" << endl <<
    "Usage: " << endl <<
    "  henry [command] [options...]" << endl <<
    "        compile_kb [-o output_file] [-i text_axiom_file] < text_axiom_file" << endl <<
    "        inference -a axiom_file|text_axiom_file@ [-w weight_bias_file] [-l log_file -L log_file_format] [-v 0|1] [-i input_file] [-p problem] [-d depth=3] [-r kb_recursion_limit=2] [< input_file]" << endl <<
    "        learn -t training_data -a axiom_file -c classifier_python_script" << endl <<
    "        classify -t test_data -a axiom_file [-w weight_bias_file] -c classifier_python_script" << endl <<
    endl <<
    "Options:" << endl <<
    "  -A    List used axioms." << endl <<
    "  -B    List used axioms in the best interpretation." << endl <<
    "  -S    List substituted variables." << endl <<
    "  -C    List constraints generated by conditioned unification." << endl <<
    "  -s    Write log files of multiple observations one by one." << endl <<
    "  -m    Do not allow multiple literals with the same name from explaining the same literal."
    "" << endl;
  
}


/*
  This is the entry point.
 */
int main( int argc, char** argv ) {

  if( 1 == argc ) { _usage(); return 1; }
  
  /* Hmm... let me see. */
  int              option;
  map<int, string> options;
  
  while( -1 != (option = getopt( argc-1, &argv[1], "t:w:123M:SABCRr:tp:g:v:a:i:d:l:sT:L:o:mcxI:Qb" )) ) {
    if( NULL == optarg ) options[ option ] = 1;
    else                 options[ option ] = optarg;
  }

  if( 0 == strcmp( argv[1], "compile_kb" ) ) {

    if( has_key( options, 'i' ) ) {
      ifstream ifs( options['i'].c_str() );
      if( !kb::compile( has_key( options, 'o' ) ? options['o'] : "stdout", ifs ) ) { WHISPER( 1, "Illegal knowledge base. Stop." << endl ); }
      ifs.close();
    } else
      if( !kb::compile( has_key( options, 'o' ) ? options['o'] : "stdout", cin ) ) { WHISPER( 1, "Illegal knowledge base. Stop." << endl ); }

  } else if( 0 == strcmp( argv[1], "inference" ) ) {

    /* Ensure the arguments are here. */
    if( !has_key( options, 'd' ) ) options[ 'd' ] = "9999";
    if( !has_key( options, 'r' ) ) options[ 'r' ] = "1";
    if( !has_key( options, 'v' ) ) options[ 'v' ] = "1";
    if( !has_key( options, 'L' ) ) options[ 'L' ] = "comp";

    //cmsg.set_verbose_level( lexical_cast<int>( options[ 'v' ] ) );
    g_verbose_level = lexical_cast<int>( options[ 'v' ] );
    
    g_progress_bar.start_thread();
    
    /* Read the input literals. */
    ie::input_storage_t inp;
    string_list_t       solved_problems;
    
    if( has_key( options, 'i' ) ) {

      ifstream ifs_input( options['i'].c_str() );
      inp.load( &solved_problems, ifs_input );
      ifs_input.close();
        
    } else
      inp.load( &solved_problems, cin );

    if( has_key( options, 'p' ) ) { solved_problems.clear(); solved_problems.push_back( options[ 'p' ] ); }

    string_list_t cmd_list;
    for(int i=0; i<argc; i++) { cmd_list.push_back( argv[i] ); }

    string        cmd = join( cmd_list, " " );

    cout << "# This output was generated by the following command: " << endl
         << "#   " << cmd << endl;
    
    foreach( string_list_t, iter_problem, solved_problems ) {

      WHISPER( 1, "Observation: " << *iter_problem << endl );

      kb::pool_t               kbp;

      if( has_key( options, 'a' ) ) {
        if( !kbp.load_axioms( options['a'] ) ) {
          WHISPER( 1, "Error: Axioms could not be loaded from " << options['a'] << "!" << endl );
          return 0;
        } }
    
      g_vm = variable_manager_t();
      ie::weighted_abduction_t waie( &kbp, cmd );

      if( !waie.get_ilp_solver().is_alive() ) {
        WHISPER( 1, "ILP solver complains about your environment." << endl );
        break;
      }
      
      waie.set_ilp_heuristics( !has_key( options, '1' ) );
      waie.set_depth_limit( lexical_cast<int>( options[ 'd' ] ) );
      waie.set_recursion_limit( lexical_cast<int>( options[ 'r' ] ) );

      if( has_key( options, 'R' ) ) waie.set_rhs_restrict( true );
      if( has_key( options, 'x' ) ) waie.set_expl_mutual_exclusiveness( true );
      if( has_key( options, 'm' ) ) waie.set_block_multiple_explanations( true );
      if( has_key( options, 'c' ) ) waie.set_block_circular_zero( true );
      if( has_key( options, 'b' ) ) waie.set_bc_from_same_prefix( true );
      
      waie.set_diet_mode( "full" == options[ 'L' ] ? false : true );
        
      ofstream ofs_ilp;
      
      if( has_key( options, 'I' ) ) {
        ofs_ilp.open( options['I'].c_str() );
        waie.set_ilp_log( ofs_ilp );
      }

      if( has_key( options, 'T' ) ) waie.set_timeout( lexical_cast<double>( options[ 'T' ] ) );
      if( has_key( options, 'M' ) ) waie.get_ilp_solver().set_threads( lexical_cast<int>( options[ 'M' ] ) );
      waie.get_ilp_solver().set_mip_start( !has_key( options, '2' ) );

      literal_list_t llt;
      int            ret;
      if( 0 != (ret = inp.parse_input( &llt, *iter_problem) ) ) {
        if( INPUT_ERROR_PROBLEM_NOT_FOUND == ret ) { WHISPER( 1, "Could not find the problem " << *iter_problem << ". Stop." << endl ); }
        continue;
      }
      
      waie.set_problem( *iter_problem, llt );
      
      WHISPER( 1, "Preprocessing..." << endl );
      
      if( !waie.ready_set() ) {
        cerr << endl;
        WHISPER( 1, "Timeout. Henry will just print out observations as the best solution." << endl );
        waie.print_observations();
      } else {
        WHISPER( 1, "Finding the best interpretation..." << endl );
        waie.go();
      }

      if( has_key( options, 'l' ) ) {

        ofstream ofs( (options[ 'l' ] + (has_key( options, 's' ) ? (*iter_problem) : "")).c_str() );
        waie.draw_graph( ofs, "full" == options[ 'L' ] ? FullGraph : ("comp" == options[ 'L' ] ? ComplexGraph : SimpleGraph ) );
        ofs.close();

      }

      if( has_key( options, 'B' ) ) waie.print_axioms( true );
      else if( has_key( options, 'A' ) ) waie.print_axioms();
      if( has_key( options, 'S' ) ) waie.print_variable_substitutions();
      if( has_key( options, 'C' ) ) waie.print_constraints();
      if( has_key( options, 'Q' ) ) waie.print_ilp();
      
      if( has_key( options, 'I' ) )
        ofs_ilp.close();
      
    }

    g_progress_bar.kill_thread();

  } else if( 0 == strcmp( argv[1], "learn" ) ) {
    if( !has_key( options, 't' ) || !has_key( options, 'a' ) || !has_key( options, 'c' ) ) { _usage(); return 0; }

    const char *p_delimiters = "(\") \n";
    char  line[1024];
    int   depth = 0;
    
    stack<string_list_t> stk;
    char                 buffer[1024];
    int                  point = 0;

    stk.push( string_list_t() );
    
    while( cin.getline( line, 1024 ) ) {
      for(int i=0; i<1024; i++) {
        if( 0 == line[i] ) break;
        else if( '(' == line[i] ) { buffer[ point ] = 0; stk.top().push_back( buffer ); point = 0; stk.push( string_list_t() ); }
        else if( ')' == line[i] ) { if( point>0 ) { buffer[ point ] = 0; stk.top().push_back( buffer ); point = 0; } cerr<<join(stk.top(),",")<<endl; stk.pop(); }
        else if( ' ' == line[i] ) { if( point>0 ) { buffer[ point ] = 0; stk.top().push_back( buffer ); point = 0; } }
        else buffer[ point++ ] = line[i];
      }
    }
    
  } else if( 0 == strcmp( argv[1], "classify" ) ) {
    if( !has_key( options, 't' ) || !has_key( options, 'a' ) || !has_key( options, 'c' ) ) { _usage(); return 0; }
    
    g_progress_bar.start_thread();
    g_progress_bar.kill_thread();
    
  } else _usage();
  
  return 0;
  
}
